iT邦幫忙

2022 iThome 鐵人賽

DAY 6
0

湊零錢問題是這樣的:

給出K種硬幣,他們的數值分別是C1,C2,C3...CK,每種硬幣的個數都不限.

然後給出一個總數Amount,使用最少的硬幣湊出這個Amount.如果湊不出來,則回傳-1

輸入的形式大概如下:

fun coinChange(coins:IntArray,amount:Int):Int
//coins是硬幣的面值,amount是目標金額

例如K=3 ,這三種硬幣的面額分別1,2,5,要湊出amount = 11.

則最少需要三個硬幣,分別是 5+5+1.

最佳子結構

一個具有最佳子結構的問題,所有子問題之間的解答必須互相獨立不干擾.不能因為其中一個子問題的答案影響到另外一個子問題的答案,比如硬幣的個數有數目限制,因此在解決要湊出B的子問題時,可能在A問題就已經用完所有的硬幣了,這樣AB問題之間就具有互相影響的性質,不能稱為最佳子結構.

但是這個問題硬幣的數目是無限的,也因此如果要湊出11塊錢,可以利用湊出10塊錢的子問題,再增加一枚1元硬幣來解決(如果有1元的面額),所以在無限硬幣的情況下,這個問題就具有最佳子結構.

首先我們先用暴力解的方法做做看這題動態規劃

暴力遞回

帶上前面說到的動態規劃範本

1.確定初始狀態:很簡單,題目的amount = 0的時候,返回0枚硬幣.所以初始狀況就是 0

2.確定狀態:也就是原問題與子問題之間互相變化的數值,那在解題過程中.逐漸改變貼近的就是題目的目標數額amount

3.確定選擇:導致狀態變化的動作,在這個題目就是選擇了某一枚硬幣,導致總數值逐漸往目標的amount前進.所以所有的硬幣面額就是選擇的集合.

4.明確狀態變化的dp函數:輸入值通常就是狀態轉移的狀態,返回值則是要求計算的量.

所以這題就是:輸入一個目標金額n,返回湊出目標金額的最少硬幣數目

那麼暴力破解的程式碼如下:

fun coinChange(coins: IntArray, amount: Int): Int {

    fun dp(amount: Int): Int {
        return if (amount == 0) {0}//初始狀態為0
        else if (amount < 0) {-1}
        else {
            var ans = Int.MAX_VALUE//因為求最小值,所以用Max作為初始...如果測資過大就要調整
            for (coin in coins) {
                val subProblem = dp(amount - coin)//求子問題的解答
                if (subProblem == -1) continue //湊不出來,跳過這個硬幣
                ans = minOf(ans, 1 + subProblem)//尋找出子問題中答案比較小的那個
            }
            if (ans != Int.MAX_VALUE) {//有找到解答
                ans
            } else {//沒有解答
                -1
            }
        }
    }

    return dp(amount)
}

我們來執行看看

fun main() {
    val coins = intArrayOf(1,2,5)
    println(coinChange(coins,11))
}

答案為3,由於這測資很簡單,就略過驗證流程了
明天我們會來優化這個暴力的解法


上一篇
Day 5: DP Table解法
下一篇
Day 7 : 湊零錢問題 優化版
系列文
從零開始的LeetCode生活,使用Kotlin學習刷題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
小哈片刻
iT邦研究生 4 級 ‧ 2022-09-12 23:24:33

這個例子舉得很好!

yuli iT邦新手 4 級 ‧ 2022-09-13 23:57:59 檢舉

哇,謝謝您的鼓勵!非常感謝!

我要留言

立即登入留言